#include "online-certifier/util/typeDefines.hh"
#include "online-certifier/communicator/frontendCommunicator.hh"
#include "certifier.hh"
#include "math.h"
#include <exception>
#include <fstream>
#include <sstream>


void printLoopInfo(size_t loop, size_t first, size_t last, size_t tasks, size_t errors){

    cout << "+===== loop ["<< loop << "]"<< " ======" << endl;
    cout << "| extra tasks : " << tasks << endl;
    cout << "| first       : " << first << endl;
    cout << "| last (total): " << last << endl;
    cout << "| errors      : " << errors << endl;
    cout << "+=====================" << endl;
}

void printCertificateInfo(size_t decoder_err , size_t total_decoder_tasks, size_t last,size_t max_errs ,integer& res){

    cout << "[front-end] certification succeeded!!" << endl;
    cout << "+------------------ C E R T I F I C A T I O N ------------------------" << endl;
    cout << "| corrected errors      : " << decoder_err << " out of " << total_decoder_tasks << " tasks (" << double(decoder_err)/double(total_decoder_tasks) << "%)"  << endl;
    cout << "| t. dispatched tasks   : " << last << " given " << max_errs << " max errors (" << double(max_errs)/double(last) << "%)" <<  endl;
    cout << "+---------------------------------------------------------------------" << endl;
    cout << "RESULT:" << endl << res << endl;

}



int main(int argc, char** argv){

    vector<string> arg; //user function argument
    string task_name;
    size_t grain_size;
    string kaapiTask_dir;
    string grid_data_dir;
    string machine_file_addr;
    string  decoder_addr;
    int decoder_port;
    string grid_addr;
    int grid_port;
    string job_key;
    string userFunction;
    long double error_rate;
    e_where mode;
    string grid_public_machine;
    bool amortize;
    /********************************* reading arguments ********************************/
    if(argc >= 4){


        userFunction.assign(argv[1]);
        grain_size = atoi(argv[2]); 

        int i;
        for(i = 3; i < argc ; i++)
            arg.push_back(argv[i]);
        
        
        ifstream conf;
        conf.open("frontend.conf");

        if(!conf.is_open()){
            perror("Can't read frontend.conf");
            exit(1);
        }
    
        string line;
        for(i=0; !conf.eof();){
            getline(conf, line);
            if(!(line.empty() || line[0] == '#' )){

        //        cout << line << endl;
                if(i==0)
                    task_name = line;
                else if(i==1)
                    kaapiTask_dir = line;
                else if(i==2)
                    grid_data_dir = line;
                else if(i==3)
                    machine_file_addr = line;
                else if(i==4)
                    decoder_addr = line;
                else if(i==5){
                    decoder_port = atoi(line.c_str());
                }else if(i==6)
                    grid_addr = line;
                else if(i==7){
                    grid_port = atoi(line.c_str());
                }else if(i==8)
                    job_key = line;
                else if(i==9)
                    error_rate = atof(line.c_str());
                else if(i==10){
                    if( line == "LOCAL")
                        mode=LOCAL;
                    else if(line == "GRID5000")
                        mode = GRID5000;
                    else if(line == "GRID")
                        mode = GRID;
                }
                else if(i==11)
                    grid_public_machine = line;
                else if(i==12) {
                    if(line == "yes")
                        amortize = true;
                    else 
                        amortize = false;
                }
                i++;
            }
        }
        
        if(i != 13){
            cout << "[front-end] config file error!" << endl;
            conf.close();
            exit(1);
        }
        conf.close();

    }
    else{

        cout << "Usage: frontend function_name grain_size args.." << endl;
        exit(1);
    }
    /********************************* reading arguments ********************************/

    size_t first=0, last=32,tasks=last , errors=0, decoder_err=0, max_errs=0, loop=1, decoder_tasks=0, total_decoder_tasks=0;

    if(!amortize)
        last = 73586; // all primes 
    
    
    integer res("0",10);
    
    try{


        userModularFunctionBase* f = userModularFunctionBase::initializeByName(userFunction, arg);
        certifier cert(f);


        frontendCommunicator* comm = new frontendCommunicator(f, task_name, grain_size, kaapiTask_dir, grid_data_dir, machine_file_addr, decoder_addr, decoder_port, grid_addr, grid_port,job_key,mode, grid_public_machine);

        if(comm->connectToDecoder() != SUCCESS){
            return -1;
        }

        cout << "[front-end] starting ..." << endl;
        bool certified = false;

        while(!certified){

            errors = ceil( error_rate * tasks);
            max_errs += errors;

            printLoopInfo(loop, first, last, tasks, errors);


            comm->dispatchGridTasks(first, last);
        
            decoder_tasks = tasks - errors;
            total_decoder_tasks += decoder_tasks;

            cout << "grid tasks " << last - first << endl;
            cout << "decoder shit " << decoder_tasks<< endl;
            if(comm->writeToDecoder( decoder_tasks, errors ) == ERROR)
                break;

            if(!amortize) 
                comm->readFromDecoder(&res, &decoder_err);
            
            while (true){

                e_grid_status g;
                e_status d;
                if(amortize)
                    d = comm->readFromDecoder(&res, &decoder_err);

                g = comm->checkGridStatus();
                if(g == FAILURE){
                    cerr <<"grid failure! " <<endl;
                    break; // we dont expect the decoder to return any value
                }else if(g == FINISH) 
                    cout << "grid finished. waiting for decoder results for " <<  FRONTEND_DECODER_TIMEOUT << " seconds " << endl;

                if(d == TIMEOUT)
                    break;

                if(d == TERMINATED){
                    break;
                }else if(d == DISCONNECTED){
                    comm->terminate();
                    return -1;
                }
                else if(d == SUCCESS){
                    //certify
                    if(cert.certify(&res)){
                        certified = true;
                        printCertificateInfo(decoder_err , total_decoder_tasks, last,max_errs, res);
                        break;
                    }//else certification failure
                }
            }

            
            tasks = tasks + tasks/log2(tasks);//amortize function
            if(amortize){
                first = last;
                last = first + tasks;
            }

            loop++;
        }


        comm->terminateDecoder();
        comm->terminate();
        delete comm;

    }catch(const char* err){

        cerr << "[front-end] user function error: " << err << endl;
    }catch(exception& ex ){
        cerr << "[front-end] user function error: " << ex.what() << endl;

    }catch(...){

        cerr << "[front-end] unexpected error occured!" << endl;
            perror("error");
    }

}

